<?php

	namespace org\tekuna\plugin\simpletemplate;

	use \Exception;

	use org\tekuna\base\Tekuna;
	
	use org\tekuna\core\configuration\ConfigurationException;
	
	use org\tekuna\framework\action\AbstractAction;
	use org\tekuna\framework\action\ActionEvent;
	use org\tekuna\framework\request\Request;
	use org\tekuna\framework\request\HttpRequest;
	use org\tekuna\framework\response\Response;
	use org\tekuna\framework\response\http\HttpResponse;
	use org\tekuna\framework\response\http\ContentResponse;

	
	/**
	 * This abstract action class provides templating at action level. Action classes
	 * extending this class must define a template for rendering in the configuration:
	 * 
	 * <action
	 *    pattern="..."
	 *    class="..."
	 *    simple-template:template="action-template.php"
	 *    simple-template:plugin="myPlugin" />
	 *    
	 * The referenced template is a regular PHP file that is executed in the
	 * classe's context. By calling $this -> property or $this -> method() you
	 * can access properties or methods of the class respectively.
	 * 
	 * If the plugin attribute is given, the template is loaded from out of the
	 * defined plugin.
	 * 
	 * The overridden execute method returns always a ContentResponse. You can 
	 * access the Response object by calling getResponse() (e.g. for setting
	 * the status code).
	 */
	abstract class SimpleTemplateAction extends AbstractAction {
		
		private $objResponse;
		
		
		/**
		 * This method is called by the execute() method. Here the actual action
		 * code is implemented. You do not need to return anything - the template
		 * execution is done by the calling execute() method.
		 * 
		 * @param ActionEvent $objActionEvent the current ActionEvent
		 * @param Request $objRequest the current Request
		 * @return void
		 */
		abstract function executeTemplate(ActionEvent $objActionEvent, Request $objRequest);
		
		
		/**
		 * This method implements the template rendering and calls the executeTemplate()
		 * method you have to implement. The method was marked final to avoid
		 * accidental overriding and thus removing the templating logic.
		 * 
		 * @param ActionEvent $objActionEvent the current ActionEvent
		 * @param Request $objRequest the current Request
		 * @throws ConfigurationException if there is no template defined for the action or the template does not exist.
		 * @return ContentResponse with rendered template
		 */
		final public function execute(ActionEvent $objActionEvent, Request $objRequest) {
			
			// prepare response
			$this -> objResponse = new ContentResponse();
			
			// call the inner execute
			$mExecuteReturn = $this -> executeTemplate($objActionEvent, $objRequest);
			
			// use return value as response if available
			if (is_object($mExecuteReturn) && $mExecuteReturn instanceof Response) {
				
				Tekuna :: getLogger(__CLASS__) -> info("executeTemplate() returned an object of type Response. Omitting Templating.");
				return $mExecuteReturn;
			}
			
			// get template file
			$sTemplateFile = $objActionEvent -> getActionElement() -> getAttribute('simple-template', 'template') -> getValue();
			
			// load script from plugin if plugin defined
			if ($objActionEvent -> getActionElement() -> hasAttribute('simple-template', 'plugin')) {
				
				// load the plugin
				$sPluginKey = $objActionEvent -> getActionElement() -> getAttribute('simple-template', 'plugin') -> getValue();
				$objPlugin = $this -> getApplicationContext() -> getPluginManager() -> getPlugin($sPluginKey);
				
				// use a plugin resource
				$sTemplateFile = $objPlugin -> getResourceFilename($sTemplateFile);
			}
			
			// template file must exist
			if (! is_file($sTemplateFile) || ! is_readable($sTemplateFile)) {
				
				throw new ConfigurationException("The referenced template file '$sTemplateFile' does not exist or is not readable.");
			}

			// log some information
			Tekuna :: getLogger(__CLASS__) -> info("Rendering response with template '$sTemplateFile'.");
			
			// render the template
			$sRenderedContent = $this -> renderTemplate($sTemplateFile, $objActionEvent, $objRequest);
			
			// update response with rendered content
			$this -> objResponse -> setContent($sRenderedContent);
			
			// return the response
			return $this -> objResponse;
		}
		
		
		/**
		 * @return ContentResponse returns the prepared response that is later
		 * filled with the rendered template content.
		 */
		public function getResponse() {
			
			return $this -> objResponse;
		}
		
		
		public function setResponse(HttpResponse $objResponse) {
			
			$this -> objResponse = $objResponse;
		}
		
		
		final private function renderTemplate($sTemplateFile, ActionEvent $objActionEvent, HttpRequest $objRequest) {
			
			// convenience variables
			$context = $this -> getApplicationContext();
			$basePath = $objActionEvent -> buildActionUrl('') -> renderRelative();
			$selfPath = $objRequest -> buildCurrentRequestUrl() -> renderRelative();
			
			// start buffering
			ob_start();
			
			try {
				
				// run decorator script in this method's context
				require $sTemplateFile;
				
				// get the decorated content
				$sRenderedContent = ob_get_contents();
				ob_end_clean();
			}
			catch (Exception $objException) {
				
				// poor man's finally...
				ob_end_clean();
				throw $objException;
			}
			
			// convert the content to the internal encoding
			$sEncoding = mb_detect_encoding($sRenderedContent);
			
			if ($sEncoding === false) {
			
				throw new SimpleTemplateException("The encoding of the rendered template '$sTemplateFile' could not be detected.");
			}
			else {
				
				Tekuna :: getLogger(__CLASS__) -> info("Detected encoding '$sEncoding' for rendered template '$sTemplateFile'.");
				$sRenderedContent = mb_convert_encoding($sRenderedContent, mb_internal_encoding(), $sEncoding);
			}
			
			return $sRenderedContent;
		}		
	}
